abstract class Task {
    /**
     * unique id of the task
     */
    abstract get id(): string;
    /**
     * list of ids.
     */
    abstract get dependOn(): string[];
    /**
     * @returns whether the task is success.
     */
    abstract run(): Promise<boolean>;
    /**
     * get the progress of task.
     * return null if you don't need this.
     */
    abstract get progress(): number | null;
}

class TaskGraph {
    data: {
        [id: string]: {
            id: string;
            dependedBy: string[];
            dependOn: string[];
        };
    } = {};

    constructor(tasks: { [id: string]: Task }) {
        for (let id in tasks) {
            this.data[id] = {
                id: id,
                dependedBy: [],
                dependOn: tasks[id].dependOn,
            };
        }
        //console.log(this.data);
        for (let id in this.data) {
            this.data[id].dependOn.forEach((dependency) => {
                this.data[dependency].dependedBy.push(id);
            });
        }
    }

    /**
     * @returns the execution order.
     */
    getExecutionOrder(): string[][] {
        const result: string[][] = [];
        while (Object.keys(this.data).length != 0) {
            const topTasks = [];
            for (let id in this.data) {
                if (this.data[id].dependedBy.length == 0)
                    topTasks.push(this.data[id]);
            }

            if (topTasks.length == 0)
                throw Error("detect recursive dependency(ies)");

            const taskGroup: string[] = [];
            topTasks.forEach((task) => {
                taskGroup.push(task.id);

                task.dependOn.forEach((dependency) => {
                    this.data[dependency].dependedBy.splice(this.data[dependency].dependedBy.indexOf(task.id), 1);
                });

                delete this.data[task.id];
            });

            result.push(taskGroup);
        }
        // it works anyway i dont know why
        return result.reverse();
    }
}

class TaskManager {
    private tasks: {
        [id: string]: Task;
    } = {};

    addTask(task: Task): void {
        this.tasks[task.id] = task;
    }

    async run(): Promise<boolean> {
        const order: string[][] = new TaskGraph(this.tasks).getExecutionOrder();

        for (let index = 0; index < order.length; index++) {
            const taskPromises: Promise<boolean>[] = [];
            const taskGroup = order[index];

            taskGroup.forEach((id) => {
                const _id = id;
                const promise = this.tasks[id].run().then((value:boolean)=> {
                    if (!value) {
                        console.log(_id);
                    }
                    return value;
                });
                taskPromises.push(promise);
            });

            if ((await Promise.all(taskPromises)).indexOf(false) != -1) {
                return false;
            }
        }

        return true;
    }
}

export { Task, TaskManager };
